# Copyright (c) 2019 Presto Labs Pte. Ltd.
# Author: xguo

import logging

from coin.exchange.base.kr_rest.private_client_base import PrivateClientBase
from coin.exchange.coinone_v2.kr_rest.product import CoinoneProduct, CoinoneCurrency
from coin.exchange.coinone_v2.kr_rest.constants import native_currency_list

import coin.proto.coin_order_enums_pb2 as coin_order_pb2
from coin.proto.coin_query_pb2 import (
    AccountBalance,
    CurrencyBalance,
    ProductFillElement,
    ProductFillBundle,
    ProductOrderElement,
    ProductOrderBundle,
)
from coin.exchange.coinone_v2.kr_rest.native_private_client import (
    CoinoneNativePrivateClient,
    is_success_response_msg,
)


def coinone_order_side_to_proto(order_side):
  if order_side == 'ask':
    return coin_order_pb2.SELL_ORDER
  elif order_side == 'bid':
    return coin_order_pb2.BUY_ORDER
  else:
    raise ValueError('Unexpected fill type" %s' % order_side)


class CoinonePrivateParser(object):
  @staticmethod
  def parse_balance(update_msg):
    # Coinone query balance returns all currency balances, including those you
    # do not have (balance = 0)
    assert update_msg['errorCode'] == '0'
    assert update_msg['result'] == 'success'

    currency_balance_list = []
    for native_currency in native_currency_list:
      balance_info = update_msg.get(native_currency)
      if balance_info is None:
        logging.error('Balance not found! %s', native_currency)
        continue
      currency = CoinoneCurrency.FromStrNativeCurrency(native_currency)
      total = float(balance_info['balance'])
      available = float(balance_info['avail'])
      hold = total - available
      currency_balance = CurrencyBalance(
          currency=currency.currency,
          currency_native=currency.native_currency,
          total=total,
          available=available,
          hold=hold,
      )
      currency_balance_list.append(currency_balance)

    return AccountBalance(exchange='Coinone', each_balance=currency_balance_list)

  @staticmethod
  def parse_fill(msg, product):
    """
    {
      "errorCode": "0",
      "completeOrders": [
      {
        "orderId": "28017adc-1e4d-11e9-9ec7-00e04c3600d7",
        "fee": "5.15",
        "timestamp": "1557913492",
        "price": "515.0",
        "qty": "10.0",
        "feeRate": "0.001",
        "type": "ask"
      },
      {
        "orderId": "28011fd3-1e4d-11e9-9ec7-00e04c3600d7",
        "fee": "5.0",
        "timestamp": "1557911241",
        "price": "500.0",
        "qty": "10.0",
        "feeRate": "0.001",
        "type": "ask"
      }
      ],
      "result": "success"
    }
    """
    assert is_success_response_msg(msg), msg
    currency_fill_list = []
    fill_list = msg['completeOrders']
    for update_msg in fill_list:
      side = coinone_order_side_to_proto(update_msg['type'])
      order_id = str(update_msg['orderId'])
      timestamp = int(float(update_msg['timestamp']) * 1e9)
      price = update_msg['price']
      qty = update_msg['qty']
      fill_id = '%s_%s_%s_%s_%s_%s' % (
          product.symbol, order_id, update_msg['type'], timestamp, price, qty)
      currency_fill = ProductFillElement(symbol=product.symbol,
                                         price=float(price),
                                         qty=float(qty),
                                         fee=float(update_msg['fee']),
                                         order_id=order_id,
                                         exchange_order_id=order_id,
                                         fill_id=fill_id,
                                         fill_timestamp=timestamp,
                                         side=side)
      currency_fill_list.append(currency_fill)

    return ProductFillBundle(exchange='Coinone',
                             symbol=product.symbol,
                             each_fill=currency_fill_list)

  @staticmethod
  def parse_orders(msg, product):
    """
    {
      "orderId": "280165a1-1e4d-11e9-9ec7-00e04c3600d7",
      "index": "4",
      "timestamp": "1557912905",
      "price": "514.0",
      "qty": "10.0",
      "feeRate": "0.001",
      "type": "ask"
    }
    """
    order_list = []
    for update_elem in msg['limitOrders']:
      order_id = str(update_elem['orderId'])
      currency_order = ProductOrderElement(symbol=product.symbol,
                                           price=float(update_elem['price']),
                                           qty=float(update_elem['qty']),
                                           order_id=order_id,
                                           exchange_order_id=order_id,
                                           created_timestamp=int(
                                               float(update_elem['timestamp']) * 1e9),
                                           side=coinone_order_side_to_proto(update_elem['type']))
      order_list.append(currency_order)
    return ProductOrderBundle(
        exchange='Coinone',
        symbol=product.symbol,
        each_order=order_list,
    )


class CoinonePrivateClient(PrivateClientBase):
  ProductType = CoinoneProduct

  def __init__(self, key_file):
    super().__init__()
    self.nprivc = CoinoneNativePrivateClient(key_file=key_file)

  def query_account_balance_impl(self):
    update = self.nprivc.query_account_balance()
    update.msg = CoinonePrivateParser.parse_balance(update.msg)
    return update

  def query_fills_impl(self, product):
    currency = product.native_symbol
    update = self.nprivc.query_complete_orders(currency=currency)
    update.msg = CoinonePrivateParser.parse_fill(update.msg, product)
    return update

  def query_list_orders_impl(self, product):
    currency = product.native_symbol
    update = self.nprivc.query_orders(currency)
    update.msg = CoinonePrivateParser.parse_orders(update.native_msg, product)
    return update

  def query_submit_impl(self, product, side, price, qty):
    raise NotImplementedError()

  def query_cancel_impl(self, product, order_id):
    raise NotImplementedError()

  def query_cancel_product_impl(self, product):
    raise NotImplementedError()

  def query_account_list_orders_impl(self):
    raise NotImplementedError()

  def query_account_fills_impl(self):
    raise NotImplementedError()

  def query_account_position_impl(self):
    raise NotImplementedError()
